#pragma once

#include <vex/core/as_intrusive.h>
#include <vex/iterable/contract/range.h>

namespace tests {
    namespace vi = vex::iterable;
    namespace vic = vex::iterable::contract;

    class forward_range_call_explicit {
        int m_client_called;
    public:
        forward_range_call_explicit() : m_client_called(0) {}

        int call_trivial(vex::iterable::contract::range<>* p_range) 
        {
            p_range->move_front();
            return ++m_client_called;
        }

        template< class TRef >
        int call_output(vex::iterable::contract::range<vex::iterable::output, TRef>* p_range) 
        {
            if (p_range->move_front()) {
                p_range->put(typename std::decay<TRef>::type());
            }
            ++m_client_called;
            return this->call_trivial(p_range);
        }

        template< class TRef>
        int call_one_pass(vex::iterable::contract::range<vex::iterable::one_pass, TRef>* p_range) 
        {
            if (p_range->move_front()) {
                p_range->front();
            }
            ++m_client_called;
            return this->call_trivial(p_range);
        }

        template< class TRef >
        int call_forward(vex::iterable::contract::range<vex::iterable::forward, TRef>* p_range) 
        {
            if (auto t_saved = vex::as_intrusive_from_out(p_range->save())) {
                // iterable successfully cloned
                t_saved->move_front();
            }

            ++m_client_called;
            return this->call_one_pass(p_range);
        }

        template< class TRef >
        int call_bidirectional(vex::iterable::contract::range<vex::iterable::bidirectional_range_tag, TRef>* p_range) 
        {
            if (p_range->move_back()) {
                p_range->back();
            }

            ++m_client_called;
            return this->call_forward(p_range);
        }

        template< class TRef>
        int call_random_access(vex::iterable::contract::range<vex::iterable::random_access_range_tag, TRef>* p_range) 
        {
            if (p_range->size() > 0u) {
                p_range->at(0);
            }

            ++m_client_called;
            return this->call_bidirectional(p_range);
        }
    };

    class forward_range_call {
        int m_client_called;
    public:
        forward_range_call() : m_client_called(0) {}

        int call(vex::iterable::contract::range<>* p_range) 
        {
            p_range->move_front();
            return ++m_client_called;
        }

        template< class TRef >
        int call(vex::iterable::contract::range<vex::iterable::output, TRef>* p_range) 
        {
            if (p_range->move_front()) {
                p_range->put(typename std::decay<TRef>::type());
            }
            ++m_client_called;
            vex::iterable::contract::range<vex::iterable::trivial>* t = p_range;
            return this->call(t);
        }

        template< class TRef>
        int call(vex::iterable::contract::range<vex::iterable::one_pass, TRef>* p_range) 
        {
            if (p_range->move_front()) {
                p_range->front();
            }
            ++m_client_called;
            vex::iterable::contract::range<vex::iterable::trivial>* t = p_range;
            return this->call(t);
        }

        template< class TRef >
        int call(vex::iterable::contract::range<vex::iterable::forward, TRef>* p_range) 
        {
            if (auto t_saved = vex::as_intrusive_from_out(p_range->save())) {
                // iterable successfully enerated
                t_saved->move_front();
            }

            ++m_client_called;
            vex::iterable::contract::range<vex::iterable::one_pass, TRef>* t = p_range;
            return this->call(t);
        }

        template< class TRef >
        int call(vex::iterable::contract::range<vex::iterable::bidirectional_range_tag, TRef>* p_range) 
        {
            if (p_range->move_back()) {
                p_range->back();
            }

            ++m_client_called;
            vic::range<vex::iterable::forward, TRef>* t = p_range;
            return this->call(t);
        }

        template< class TRef>
        int call(vex::iterable::contract::range<vex::iterable::random_access_range_tag, TRef>* p_range) 
        {
            if (p_range->size() > 0u) {
                p_range->at(0);
            }

            ++m_client_called;
            vex::iterable::contract::range<vex::iterable::bidirectional_range_tag, TRef>* t = p_range;
            return this->call(t);
        }
    };
}